/*
 * @Author: xwd  1324156920@qq.com
 * @Date: 2024-08-29 17:41:42
 * @LastEditors: Xi Weidong
 * @LastEditTime: 2024-08-31 14:54:40
 * @FilePath: \plugins\src\plugins\ContourLineAlgorithm.js
 * @Description: ContourLineAlgorithm-[等高线生成算法]
 *
 * Copyright (c) 2024 by ${1324156920@qq.com}, All Rights Reserved.
 */

import * as Cesium from "cesium";
import arrayMaxValue from "@/common/arrayMaxValue.js";
import arrayMinValue from "@/common/arrayMinValue.js";
import ContourLine from "@/plugins/ContourLine.js";
import ContourLineWorker from "@/plugins/ContourLineWorker.js?worker";

/**
 * defaultValue
 */
const defaultColor = Cesium.Color.WHITE;
const defaultWidth = 2;
const defaultSpacing = 100;
const defaultGranularity = Math.PI / Math.pow(2, 11) / 32.0;

/**
 * @typedef {Object} ContourLineAlgorithm.ConstructorOptions
 *
 * @property {Number} spacing 等间距
 * @property {Number} width 宽度
 * @property {Cesium.Color} color 颜色
 * @property {Number} granularity 细分程度,建议取2的N次方,默认为32
 */

/**
 * 基于空间离散点生成等高线
 *
 * @class
 * @alias ContourLineAlgorithm
 * @param {Cesium.Viewer} viewer 三维场景对象
 * @param {ContourLineAlgorithm.ConstructorOptions} [options] 配置项
 * @constructor
 */
function ContourLineAlgorithm(viewer, options) {
  options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);
  //>>define properties
  this._viewer = viewer;
  this._spacing = Cesium.defaultValue(options.spacing, defaultSpacing);
  this._width = Cesium.defaultValue(options.width, defaultWidth);
  this._color = Cesium.defaultValue(options.color, defaultColor);
  this._granularity = Cesium.defaultValue(
    options.granularity,
    defaultGranularity
  );
  //>>temp properties
  this._worker = null;
}

/**
 * 成员属性
 */
Object.defineProperties(ContourLineAlgorithm.prototype, {
  viewer: {
    get() {
      return this._viewer;
    },
  },
  spacing: {
    get: function () {
      return this._spacing;
    },
    set: function (value) {
      if (Cesium.defined(value) && typeof value === "number") {
        this._spacing = value;
      }
    },
  },
  width: {
    get: function () {
      return this._width;
    },
    set: function (value) {
      if (Cesium.defined(value) && typeof value === "number") {
        this._width = value;
      }
    },
  },
  color: {
    get: function () {
      return this._color;
    },
    set: function (value) {
      if (Cesium.defined(value) && value instanceof Cesium.Color) {
        this._color = value;
      }
    },
  },
  granularity: {
    get: function () {
      return this._granularity;
    },
    set: function (value) {
      if (Cesium.defined(value) && typeof value === "number") {
        this._granularity = value;
      }
    },
  },
});

/**
 * 渲染等值线
 * @param {ContourLine[]} contourLines 等值线列表
 * @param {number[]} contourLineTable 等值线值表
 * @param {{appearance:Cesium.Appearance,color:Cesium.Color,width:Number,collection:Cesium.PrimitiveCollection}} [options] 配置项
 * @returns {Cesium.GroundPolylinePrimitive[]} 等值线图元列表
 */
ContourLineAlgorithm.prototype.renderContourLines = function (
  contourLines,
  contourLineTable,
  options
) {
  let appearance = options.appearance;
  if (!Cesium.defined(appearance)) {
    appearance = new Cesium.MaterialAppearance({
      material: Cesium.Material.fromType("Color", {
        color: this._color,
      }),
    });
  }
  const color = Cesium.defaultValue(options?.color, Cesium.Color.RED);
  const width = Cesium.defaultValue(options?.width, 2);
  const polylinePrimitiveList = contourLines.map((contourLine) => {
    return contourLine.createGroundPolylinePrimitive(appearance, {
      color: color,
      width: width,
    });
  });
  //添加到场景中
  if (
    Cesium.defined(options.collection) &&
    options.collection instanceof Cesium.PrimitiveCollection
  ) {
    polylinePrimitiveList.map((contourLine) =>
      options.collection.add(contourLine)
    );
  }
  return polylinePrimitiveList;
};

/**
 * 高程计算
 * @param {Cesium.TerrainProvider} terrainProvider 地形服务
 * @param {Cesium.Cartographic[]} cartographicList 采样点列表
 * @private
 */
ContourLineAlgorithm.prototype._sampleTerrainMostDetailed = function (
  terrainProvider,
  cartographicList
) {
  return new Promise(async (resolve, reject) => {
    try {
      if (Cesium?.when) {
        const promise = Cesium.sampleTerrainMostDetailed(
          terrainProvider,
          cartographicList
        );
        Cesium.when(promise, function (updatedPositions) {
          resolve(updatedPositions);
        });
      } else {
        const updatedPositions = await Cesium.sampleTerrainMostDetailed(
          terrainProvider,
          cartographicList
        );
        resolve(updatedPositions);
      }
    } catch (err) {
      reject(err);
    }
  });
};

/**
 * 执行算法
 * @param {Cesium.Cartesian3[]} positions 区域边界
 */
ContourLineAlgorithm.prototype.doAlgorithm = function (positions) {
  return new Promise(async (resolve, reject) => {
    //判断地形服务是否存在
    const terrainProviderName =
      this._viewer.terrainProvider.__proto__.constructor.name;
    if (terrainProviderName === "EllipsoidTerrainProvider") {
      reject("invalid TerrainProvider service,type is" + terrainProviderName);
    }
    try {
      const tessellation = this._doTessellation(positions);
      //地形高程计算-最大+最小高程值
      const cartesian3List = tessellation.positions;
      const cartographicList = cartesian3List.map((cartesian3) =>
        Cesium.Cartographic.fromCartesian(cartesian3, Cesium.Ellipsoid.WGS84)
      );
      const updatedCartographicList = await this._sampleTerrainMostDetailed(
        this._viewer.terrainProvider,
        cartographicList
      );
      const altitudeList = updatedCartographicList.map(
        (cartographic) => cartographic.height
      );
      const maxAltitude = arrayMaxValue(altitudeList),
        minAltitude = arrayMinValue(altitudeList);
      //预校验
      if (maxAltitude - minAltitude < this._spacing) {
        throw new Cesium.DeveloperError(
          "spacing is too large,no contour-line was generated!"
        );
      }
      //创建worker,执行计算
      const that = this;
      this._worker = new ContourLineWorker();
      this._worker.onmessage = function (event) {
        try {
          if (event?.data?.code === 200) {
            //监听消息
            const { tinSet, maxAltitude, minAltitude } = event.data?.data || {};
            const result = that._createContourLineByTinList(
              tinSet,
              that._spacing,
              maxAltitude,
              minAltitude
            );
            //返回等高线构建结果
            resolve(result);
          } else {
            reject(event?.data?.msg || "generate failed!");
          }
        } catch (error) {
          reject(error?.message || "generate failed!");
        } finally {
          that._worker.terminate();
          that._worker = null;
        }
      };
      this._worker.postMessage({
        //发送消息
        indieList: tessellation.indices,
        vertexList: tessellation.vertexes,
        altitudeList: altitudeList,
        maxAltitude: maxAltitude,
        minAltitude: minAltitude,
      });
    } catch (err) {
      reject(err?.message || "doAlgorithm failed!");
    }
  });
};

/**
 * 取消算法执行
 */
ContourLineAlgorithm.prototype.cancelAlgorithm = function () {
  if (this._worker) {
    this._worker.terminate();
  }
};

/**
 * 曲面细分
 * @param {Cesium.Cartesian3[]} positions 世界坐标序列
 * @returns {{positions:Cesium.Cartesian3[],indices:Number[],vertexes:Number[]}} 结果:positions-组装后的三角网顶点序列;indices-索引;vertexes-原始顶点
 * @private
 */
ContourLineAlgorithm.prototype._doTessellation = function (positions) {
  const polygon = Cesium.PolygonGeometry.fromPositions({
    positions: positions,
    vertexFormat: Cesium.PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, //granularity越大,划分出来的三角网就越多
    granularity: this._granularity,
  });
  const polygonGeometry = Cesium.PolygonGeometry.createGeometry(polygon);
  const indieList = polygonGeometry.indices; //逆时针顺序-【1个索引下标对应xyz三个分量】
  const vertexList = polygonGeometry.attributes.position.values; //【所有xyz分量的集合】
  const positionList = [];
  for (let i = 0; i < indieList.length; i++) {
    const indieItem = indieList[i];
    //Cartesian3顶点
    const cartesian3 = new Cesium.Cartesian3(
      vertexList[indieItem * 3],
      vertexList[indieItem * 3 + 1],
      vertexList[indieItem * 3 + 2]
    );
    //Cartesian3
    positionList.push(cartesian3);
  }
  return {
    positions: positionList,
    indices: indieList,
    vertexes: vertexList,
  };
};

/**
 * TIN不规则三角网生成等高线
 * @param { TIN[] } tinSet Tin不规则三角网
 * @param { number } spacing 等间距
 * @param { number } maxAltitude 最大高程
 * @param { number } minAltitude 最小高程
 * @returns {{contourLines:ContourLine[],contourLineTable:number[]} } 等值线列表+等值线表
 */
ContourLineAlgorithm.prototype._createContourLineByTinList = function (
  tinSet,
  spacing,
  maxAltitude,
  minAltitude
) {
  //计算等值线表
  const contourLineTable = this._getContourLineTable(
    maxAltitude,
    minAltitude,
    spacing
  );
  //生成并返回等值线
  const contourLines = this._generateContourLines(tinSet, contourLineTable);
  return {
    contourLines,
    contourLineTable,
  };
};

/**
 * 构建等高线分级列表
 * @param { number } maxAltitude 最大高程
 * @param { number } minAltitude 最小高程
 * @param { number } spacing 等高线间距
 */
ContourLineAlgorithm.prototype._getContourLineTable = function (
  maxAltitude,
  minAltitude,
  spacing
) {
  const diff = maxAltitude - minAltitude;
  if (diff <= spacing) {
    return [minAltitude];
  }
  //等高线分级
  const total = Math.floor(diff / spacing);
  const levels = [];
  for (let i = 0; i < total; i++) {
    levels.push(minAltitude + spacing * i);
  }
  return levels;
};
/**
 * 生成等值线
 * @param { Array<TIN> } tinSet 带有邻接边关系的不规则三角网集合
 * @param { Array<number> } contourLineTable 等高线值的分级列表
 * @returns { ContourLine[] } 等高线列表
 */
ContourLineAlgorithm.prototype._generateContourLines = function (
  tinSet,
  contourLineTable
) {
  if (!Array.isArray(contourLineTable) || contourLineTable.length === 0)
    return [];
  //构建并返回等高线列表
  const contourList = [];
  for (let i = 0; i < contourLineTable.length; i++)
    contourList.push(
      this._createContourLineByAltitude(tinSet, contourLineTable[i])
    );
  return contourList;
};

/**
 * 生成同高程的N段等值线
 * @param {Array<TIN> } tinSet 带有邻接边关系的不规则三角网集合
 * @param {number} altitude 等高线高程值
 * @private  { ContourLine } 等高线实例
 */
ContourLineAlgorithm.prototype._createContourLineByAltitude = function (
  tinSet,
  altitude
) {
  //创建等高线实例ContourLine
  const tinSetSize = tinSet.length;
  const linePositionsList = [];
  const contourLine = new ContourLine(altitude, linePositionsList);
  //从任意三角形的任意边两个端点开始检索
  for (let i = 0; i < tinSetSize; i++) {
    const tinItem = tinSet[i];
    const curLinePositions = [];
    const startPointIndie = tinItem.vertexes[0]; //起始点的顶点索引值
    this._deepRetrievalTINItem(
      curLinePositions,
      tinItem,
      altitude,
      startPointIndie
    );
    //优化点1-去除空列表-[避免绘制阶段长时间循环空列表]
    if (curLinePositions.length > 0) {
      //追加到现有的ContourLine坐标中
      linePositionsList.push(curLinePositions);
    }
  }
  return contourLine;
};

/**
 * 深度检索TIN三角形-[tin本身以及邻接TIN三角形]
 * @param { Array<Cesium.Cartesian3> } linePositions 等高线顶点数组-[引用]
 * @param { TIN } tinItem 三角形
 * @param { number } height 等高线高程值
 * @param { number } startPointIndie 起始点的顶点索引值
 */
ContourLineAlgorithm.prototype._deepRetrievalTINItem = function (
  linePositions,
  tinItem,
  height,
  startPointIndie
) {
  //TIN顶点
  const vertexes = tinItem.vertexes;
  //当前TIN相邻的三角形
  const adjoinTINs = tinItem.adjointTINs;
  //TIN顶点索引下标
  // const indices = tinItem.indices;
  //搜索当前三角形
  let lastTwoPointIdx = []; //记录最后一条与等高线相交的线段端点下标
  for (let k = 0; k < 3; k++) {
    const p1 = vertexes[k % 3],
      p2 = vertexes[(k + 1) % 3];
    //判断是否穿过当前边
    if (isCrossLine(p1, p2, height)) {
      //tips:穿过当前边-计算等高线与边的交点,并记录顶点
      const p1WithHeight = transPoint2Cartesian(p1),
        p2WithHeight = transPoint2Cartesian(p2);
      const crossPoint = getMiddlePoint(p1WithHeight, p2WithHeight, height);
      linePositions.push(crossPoint);
      lastTwoPointIdx.splice(0, lastTwoPointIdx.length);
      lastTwoPointIdx.push(k % 3, (k + 1) % 3);
    }
    // else {
    //     //tips:不穿过当前边-[判断下一条边]
    //     continue;
    // }
  }
  //标记已经检索过的TIN-[等高线值]
  tinItem.flag = height;
  //判断是否需要检索邻接三角形
  if (lastTwoPointIdx.length === 0) {
    return linePositions;
  }
  //查找邻接三角形下标索引
  const adjoinTinItemIdx = [0, 1, 2].filter(
    (idx) => lastTwoPointIdx.indexOf(idx) === -1
  )[0];
  if (!Cesium.defined(adjoinTinItemIdx) || adjoinTinItemIdx < 0) {
    return linePositions;
  }
  //判断是否到达起始顶点
  if (vertexes[adjoinTinItemIdx] === startPointIndie) {
    return linePositions; //达到起始点-终止
  }
  //获取邻接三角形
  const adjoinTinItem = adjoinTINs[adjoinTinItemIdx];
  //找不到邻接三角形,直接在tinSet中查找下一个
  if (!Cesium.defined(adjoinTinItem)) {
    return linePositions;
  }
  //判断是否为已经搜索过的TIN三角面
  if (adjoinTinItem.flag === height) {
    return linePositions; //已经查找过,直接在tinSet中查找下一个
  }
  //遍历邻接三角形
  return this._deepRetrievalTINItem(
    linePositions,
    adjoinTinItem,
    height,
    startPointIndie
  );
};

/**
 * 将Point转换为带高程的Cesium.Cartesian3
 * @param { Point } point TIN的顶点
 */
function transPoint2Cartesian(point) {
  const cartesian3 = point.position;
  const cartographic = Cesium.Cartographic.fromCartesian(
    cartesian3,
    Cesium.Ellipsoid.WGS84
  );
  cartographic.height = point.w || 0;
  return Cesium.Cartographic.toCartesian(cartographic, Cesium.Ellipsoid.WGS84);
}

/**
 * 判断高程值为counterValue的等高线是否穿过p1与p2所成线段
 * @param { Point } p1 线段端点1
 * @param { Point } p2 线段端点2
 * @param { number } counterValue 等高线高程值
 * @returns { boolean } true:是;false:否
 */
function isCrossLine(p1, p2, counterValue) {
  return (p1.w - counterValue) * (p2.w - counterValue) <= 0;
}

/**
 * [计算两个Cesium.Cartesian3的中点]计算高程值为counterValue的等高线与p1与p2所成线段的交点
 * @param { Point } p1 线段端点1
 * @param { Point } p2 线段端点2
 * @param { number } altitude 等高线高程值
 * @returns { Cesium.Cartesian3 } 交点坐标
 */
function getMiddlePoint(p1, p2, altitude) {
  const cartographicP1 = Cesium.Cartographic.fromCartesian(p1),
    cartographicP2 = Cesium.Cartographic.fromCartesian(p2);
  const lnglatP1 = {
    longitude: Cesium.Math.toDegrees(cartographicP1.longitude),
    latitude: Cesium.Math.toDegrees(cartographicP1.latitude),
    altitude: cartographicP1.height,
  };
  const lnglatP2 = {
    longitude: Cesium.Math.toDegrees(cartographicP2.longitude),
    latitude: Cesium.Math.toDegrees(cartographicP2.latitude),
    altitude: cartographicP2.height,
  };
  //计算起始点、终止点
  const startPt = lnglatP1.altitude < lnglatP2.altitude ? lnglatP1 : lnglatP2;
  const endPt = lnglatP1.altitude > lnglatP2.altitude ? lnglatP1 : lnglatP2;
  //计算中点
  const denominator =
    (altitude - startPt.altitude) / (endPt.altitude - startPt.altitude);
  const midLnglat = {
    longitude:
      startPt.longitude + (endPt.longitude - startPt.longitude) * denominator,
    latitude:
      startPt.latitude + (endPt.latitude - startPt.latitude) * denominator,
    altitude: altitude,
  };
  //返回中点
  return Cesium.Cartesian3.fromDegrees(
    midLnglat.longitude,
    midLnglat.latitude,
    midLnglat.altitude
  );
}

export default ContourLineAlgorithm;
